/**
@author		Jong Min, Lee
@date		  02/06/2014
@breif		toolbox stack control

copyright(c) Tae Young Soft. All right reserved. 2014
*/
#include "stdafx.h"
#include "Taurus_Viewer/ToolboxStack.h"
#include <algorithm>
#include "Taurus_ToolData/ToolboxData.h"
#include "Taurus_Util/Macros.h"
#include "Taurus_Util/Errors.h"
#include "Taurus_Viewer/TBLayoutInfo.h"
#include "Taurus_Viewer/Resource.h"

IMPLEMENT_DYNAMIC(CToolboxStack, CWnd)

CToolboxStack::CToolboxStack()
{

}

CToolboxStack::~CToolboxStack()
{
  removeAllStackItems();
}

DWORD CToolboxStack::openToolboxes(const CToolboxData** arrToolboxData, 
                                   const unsigned nNumToolboxData, 
                                   BOOL bRedraw)
{
  if(nNumToolboxData > 0 && arrToolboxData == NULL)
    return ERROR_INVALID_PARAMETER;

  //hide all toolbox stack items and lock redraw
  BOOL bThisCreated = ::IsWindow(this->GetSafeHwnd());
  UINT nFlag = SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW | SWP_HIDEWINDOW;
  if(bThisCreated)
  {
    for_each(m_listItems.begin(), m_listItems.end(),
      [&nFlag](CToolboxStackItem* pItem)
    {
      pItem->wndToolbox.SetWindowPos(NULL, 0, 0, 0, 0, nFlag);
      pItem->wndToolbox.lockRearrange();
      pItem->btnTitle.SetWindowPos(NULL, 0, 0, 0, 0, nFlag);
      pItem->bOpen = FALSE;
    });
  }

  //recycle toolbox stack items
  unsigned nNumItemsInUse = 0;
  DWORD dwError = ERROR_SUCCESS;
  auto itemPos = m_listItems.begin();
  auto endPos = m_listItems.end();
  unsigned nToolboxIndex = 0;
  for(;nToolboxIndex < nNumToolboxData && itemPos != endPos; ++nToolboxIndex)
  {
    const CToolboxData* pToolboxData = arrToolboxData[nToolboxIndex];
    if(pToolboxData == NULL)
      continue;

    //open a toolbox data in toolbox stack item
    CToolboxStackItem* pItem = (*itemPos);
    dwError = openToolbox(*pItem, *pToolboxData);
    if(dwError != ERROR_SUCCESS)
      break;

    itemPos++;
    nNumItemsInUse++;
  }

  //check result of recycling toolbox stack items
  if(dwError != ERROR_SUCCESS)
  {
    removeAllStackItems();
    if(bRedraw && bThisCreated)
      Invalidate();
    return dwError;
  }

  //need more toolbox stack items??
  for(;nToolboxIndex < nNumToolboxData; ++nToolboxIndex)
  {
    const CToolboxData* pToolboxData = arrToolboxData[nToolboxIndex];
    if(pToolboxData == NULL)
      continue;

    CToolboxStackItem* pNewItem = NULL;
    _TRY_ALLOC(pNewItem, CToolboxStackItem);
    if(pNewItem == NULL)
    {
      dwError = ERROR_NOT_ENOUGH_MEMORY;
      break;
    }

    pNewItem->strToolboxName = pToolboxData->getName();
    pNewItem->bOpen = FALSE;

    if(bThisCreated)
    {
      dwError = createToolboxStackItem(*pNewItem, IDC_TOOLBOX_STACK_ITEM + nNumItemsInUse);
      if(dwError != ERROR_SUCCESS)
      {
        destroyStackItem(*pNewItem);
        _DELETE(pNewItem);
        break;
      }
    }

    pNewItem->wndToolbox.lockRearrange();
    dwError = openToolbox(*pNewItem, *pToolboxData);
    if(dwError != ERROR_SUCCESS)
    {
      destroyStackItem(*pNewItem);
      _DELETE(pNewItem);
      break;
    }

    _TRY_BEGIN
    m_listItems.push_back(pNewItem);
    _CATCH_ALL
    destroyStackItem(*pNewItem);
    _DELETE(pNewItem);
    dwError = ERROR_NOT_ENOUGH_MEMORY;
    break;
    _CATCH_END
    nNumItemsInUse++;
  }

  //check result of creating new stack items
  if(dwError != ERROR_SUCCESS)
  {
    removeAllStackItems();
    if(bThisCreated && bRedraw)
      Invalidate();
    return dwError;
  }

  //remove stack items in no use
  itemPos = m_listItems.begin();
  std::advance(itemPos, nNumItemsInUse);
  endPos = m_listItems.end();
  while(itemPos != endPos)
  {
    destroyStackItem(*(*itemPos));
    _DELETE(*itemPos);
    m_listItems.erase(itemPos++);
  }

  if(bThisCreated)
  {
    if(m_listItems.size() > 0)
      (*m_listItems.begin())->bOpen = TRUE;

    rearrangeStackItems(FALSE);

    nFlag = SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW | SWP_SHOWWINDOW;
    for(itemPos = m_listItems.begin(), endPos = m_listItems.end(); itemPos != endPos; ++itemPos)
    {
      (*itemPos)->btnTitle.SetWindowPos(NULL, 0, 0, 0, 0, nFlag);
      (*itemPos)->wndToolbox.releaseRearrange();
      if(itemPos == m_listItems.begin())
        (*itemPos)->wndToolbox.SetWindowPos(NULL, 0, 0, 0, 0, nFlag);
    }
    if(bRedraw)
      Invalidate();
  }

  return ERROR_SUCCESS;
}

DWORD CToolboxStack::openToolbox(CToolboxStackItem& cToolboxStackItem, 
                                 const CToolboxData& cToolbox)
{
  DWORD dwError = cToolboxStackItem.wndToolbox.openToolbox(&cToolbox);
  if(dwError != ERROR_SUCCESS)
    return dwError;
  
  cToolboxStackItem.strToolboxName = cToolbox.getName();

  if(::IsWindow(cToolboxStackItem.btnTitle.GetSafeHwnd()))
  {
    cToolboxStackItem.btnTitle.SetWindowText(cToolbox.getName());
  }
  
  return ERROR_SUCCESS;
}

void CToolboxStack::openToolboxStackItem(const unsigned index)
{
  CRect rcClient;
  GetClientRect(&rcClient);
  if(rcClient.IsRectEmpty())
    return;

  BOOL bNeedRearragne = FALSE;
  unsigned nItemIndex = 0;
  for(auto itemPos = m_listItems.begin(), endPos = m_listItems.end();
      itemPos != endPos;
      ++itemPos, ++nItemIndex)
  {
    CToolboxStackItem* pItem = (*itemPos);
    if(index == nItemIndex)
    {
      if(!pItem->bOpen)
      {
        pItem->bOpen = TRUE;
        bNeedRearragne = TRUE;
      }
    }
    else
    {
      pItem->bOpen = FALSE;
    }
  }

  if(!bNeedRearragne)
    return;

  if(rcClient.Width() < rcClient.Height())
    openToolboxStackItemV();
  else
    openToolboxStackItemH();
}

void CToolboxStack::openToolboxStackItemV()
{
  CRect rcClient;
  GetClientRect(&rcClient);

  CSize csMargin = getTitleButtonMargin(m_enumResType, TRUE);
  CSize csTitleBtnSize = getTitleButtonSize(m_enumResType, TRUE);

  CPoint ptTitleBtnTl;
  ptTitleBtnTl.x = rcClient.left;
  ptTitleBtnTl.y = rcClient.top;
  for(auto itemPos = m_listItems.begin(), endPos = m_listItems.end();
      itemPos != endPos;
      ++itemPos)
  {
    CToolboxStackItem* pItem = (*itemPos);
    CRect rcNew(ptTitleBtnTl.x, ptTitleBtnTl.y,
                ptTitleBtnTl.x + csTitleBtnSize.cx,
                ptTitleBtnTl.y + csTitleBtnSize.cy);
    CRect rcCurrent;
    pItem->btnTitle.GetWindowRect(&rcCurrent);
    ScreenToClient(rcCurrent);

    if(rcCurrent != rcNew)
      pItem->btnTitle.MoveWindow(rcNew, TRUE);

    if(pItem->bOpen)
    {
      pItem->wndToolbox.SetWindowPos(
        NULL, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_SHOWWINDOW);

      CRect rcToolbox;
      pItem->wndToolbox.GetWindowRect(&rcToolbox);
      ScreenToClient(&rcToolbox);

      ptTitleBtnTl.y = rcToolbox.bottom + csMargin.cy;
    }
    else
    {
      if(pItem->wndToolbox.IsWindowVisible())
      {
        pItem->wndToolbox.SetWindowPos(
          NULL, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_HIDEWINDOW);
      }
      ptTitleBtnTl.y += (csTitleBtnSize.cy + csMargin.cy);
    }
  }
}

void CToolboxStack::openToolboxStackItemH()
{
  CRect rcClient;
  GetClientRect(&rcClient);

  CSize csMargin = getTitleButtonMargin(m_enumResType, FALSE);
  CSize csTitleBtnSize = getTitleButtonSize(m_enumResType, FALSE);

  CPoint ptTitleBtnTl;
  ptTitleBtnTl.x = rcClient.left;
  ptTitleBtnTl.y = rcClient.top;
  for(auto itemPos = m_listItems.begin(), endPos = m_listItems.end();
      itemPos != endPos;
      ++itemPos)
  {
    CToolboxStackItem* pItem = (*itemPos);
    CRect rcNew(ptTitleBtnTl.x, ptTitleBtnTl.y,
                ptTitleBtnTl.x + csTitleBtnSize.cx,
                ptTitleBtnTl.y + csTitleBtnSize.cy);
    CRect rcCurrent;
    pItem->btnTitle.GetWindowRect(&rcCurrent);
    ScreenToClient(rcCurrent);

    if(rcCurrent != rcNew)
      pItem->btnTitle.MoveWindow(rcNew, TRUE);

    if(pItem->bOpen)
    {
      pItem->wndToolbox.SetWindowPos(
        NULL, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_SHOWWINDOW);

      CRect rcToolbox;
      pItem->wndToolbox.GetWindowRect(&rcToolbox);
      ScreenToClient(&rcToolbox);

      ptTitleBtnTl.x = rcToolbox.right + csMargin.cx;
    }
    else
    {
      if(pItem->wndToolbox.IsWindowVisible())
      {
        pItem->wndToolbox.SetWindowPos(
          NULL, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_HIDEWINDOW);
      }
      ptTitleBtnTl.x += (csTitleBtnSize.cx + csMargin.cx);
    }
  }
}

DWORD CToolboxStack::createToolboxStackItem(CToolboxStackItem& cToolboxStackItem, 
                                            UINT uItemID)
{
  DWORD dwError = ERROR_SUCCESS;
  CToolbox::CreateParams stParams;
  stParams.enumResType = m_enumResType;
  stParams.bEnableNotification = TRUE;
  if(!cToolboxStackItem.wndToolbox.CreateEx(
    0, NULL, NULL, WS_CHILD, CRect(0, 0, 0, 0), this, 0, (LPVOID)&stParams))
  {
    dwError = ::GetLastError();
    if(dwError == ERROR_SUCCESS)
      dwError = ERROR_UNKNOWN;
    return dwError;
  }
  
  DWORD dwStyle = BS_ICON | BS_VCENTER | WS_CHILD | BS_PUSHBUTTON;
  if(!cToolboxStackItem.btnTitle.Create(NULL, dwStyle, CRect(0,0,0,0), this, uItemID))
  {
    dwError = ::GetLastError();
    if(dwError == ERROR_SUCCESS) dwError = ERROR_UNKNOWN;
    cToolboxStackItem.wndToolbox.DestroyWindow();
    return dwError;
  }

  cToolboxStackItem.btnTitle.SetWindowText(cToolboxStackItem.strToolboxName);

  return ERROR_SUCCESS;
}

void CToolboxStack::rearrangeStackItems(BOOL bRedraw)
{
  CRect rcClient;
  GetClientRect(&rcClient);

  if(rcClient.IsRectEmpty())
    return;

  if(rcClient.Width() < rcClient.Height())
    rearrangeStackItemsV(bRedraw);
  else
    rearrangeStackItemsH(bRedraw);
}

void CToolboxStack::rearrangeStackItemsV(BOOL bRedraw)
{
  CRect rcClient;
  GetClientRect(&rcClient);

  CSize csMargin = getTitleButtonMargin(m_enumResType, TRUE);
  CSize csTitleBtnSize = getTitleButtonSize(m_enumResType, TRUE);

  unsigned nNumItems = m_listItems.size();
  unsigned nItemIndex = 0;
  CPoint ptTitleBtnTL(rcClient.left, rcClient.top);
  for(auto itemPos = m_listItems.begin(), endPos = m_listItems.end();
      itemPos != endPos;
      ++itemPos, ++nItemIndex)
  {
    CRect rcToolbox;
    rcToolbox.left   = rcClient.left;
    rcToolbox.top    = (csTitleBtnSize.cy * (nItemIndex + 1)) +
                       (csMargin.cy       * (nItemIndex + 1));
    rcToolbox.right  = rcClient.right - csMargin.cx;
    rcToolbox.bottom = (rcClient.bottom - ((nNumItems - (nItemIndex + 1)) *
                                           (csTitleBtnSize.cy + csMargin.cy)));
    (*itemPos)->wndToolbox.MoveWindow(&rcToolbox, bRedraw);
    (*itemPos)->btnTitle.MoveWindow(ptTitleBtnTL.x,
                                    ptTitleBtnTL.y,
                                    csTitleBtnSize.cx,
                                    csTitleBtnSize.cy,
                                    bRedraw);
    if((*itemPos)->bOpen)
      ptTitleBtnTL.y = rcToolbox.bottom + csMargin.cy;
    else
      ptTitleBtnTL.y += (csTitleBtnSize.cy + csMargin.cy);
  }
}

void CToolboxStack::rearrangeStackItemsH(BOOL bRedraw)
{
  CRect rcClient;
  GetClientRect(&rcClient);

  CSize csMargin = getTitleButtonMargin(m_enumResType, FALSE);
  CSize csTitleBtnSize = getTitleButtonSize(m_enumResType, FALSE);

  unsigned nNumItems = m_listItems.size();
  unsigned nItemIndex = 0;
  CPoint ptTitleBtnTL(rcClient.left, rcClient.top);
  for(auto itemPos = m_listItems.begin(), endPos = m_listItems.end();
      itemPos != endPos;
      ++itemPos, ++nItemIndex)
  {
    CRect rcToolbox;
    rcToolbox.left    = rcClient.left + ((csTitleBtnSize.cx * (nItemIndex + 1)) +
                                         (csMargin.cx       * (nItemIndex + 1)));
    rcToolbox.top     = rcClient.top;
    rcToolbox.right   = rcClient.right - ((nNumItems - (nItemIndex + 1)) *
                                          (csTitleBtnSize.cx + csMargin.cx));
    rcToolbox.bottom  = rcClient.bottom;
    (*itemPos)->wndToolbox.MoveWindow(rcToolbox, bRedraw);
    (*itemPos)->wndToolbox.MoveWindow(ptTitleBtnTL.x,
                                      ptTitleBtnTL.y,
                                      csTitleBtnSize.cx,
                                      csTitleBtnSize.cy,
                                      bRedraw);

    if((*itemPos)->bOpen)
      ptTitleBtnTL.x = rcToolbox.right + csMargin.cx;
    else
      ptTitleBtnTL.x += (csTitleBtnSize.cx + csMargin.cx);
  }
}

CSize CToolboxStack::getTitleButtonSize(MonitorResolutionType enumResType, 
                                        BOOL bVertical) const
{
  CSize csSize;
  if(bVertical)
  {
    switch (enumResType)
    {
    case MonitorResolution_Normal:
      {
        csSize.cx = V_TB_TOOLBOX_TITLE_BTN_WIDTH_NORMAL;
        csSize.cy = V_TB_TOOLBOX_TITLE_BTN_HEIGHT_NORMAL;
      }
      break;
    case MonitorResolution_3M:
      {
        csSize.cx = V_TB_TOOLBOX_TITLE_BTN_WIDTH_3M;
        csSize.cy = V_TB_TOOLBOX_TITLE_BTN_HEIGHT_3M;
      }
      break;
    case MonitorResolution_5M:
      {
        csSize.cx = V_TB_TOOLBOX_TITLE_BTN_WIDTH_5M;
        csSize.cy = V_TB_TOOLBOX_TITLE_BTN_HEIGHT_5M;
      }
      break;
    default:
      {
        csSize.cx = csSize.cy = 0;
      }
      break;
    }
  }
  else
  {
    switch (enumResType)
    {
    case MonitorResolution_Normal:
      {
        csSize.cx = H_TB_TOOLBOX_TITLE_BTN_WIDTH_NORMAL;
        csSize.cy = H_TB_TOOLBOX_TITLE_BTN_HEIGHT_NORMAL;
      }
      break;
    case MonitorResolution_3M:
      {
        csSize.cx = H_TB_TOOLBOX_TITLE_BTN_WIDTH_3M;
        csSize.cy = H_TB_TOOLBOX_TITLE_BTN_HEIGHT_3M;
      }
      break;
    case MonitorResolution_5M:
      {
        csSize.cx = H_TB_TOOLBOX_TITLE_BTN_WIDTH_5M;
        csSize.cy = H_TB_TOOLBOX_TITLE_BTN_HEIGHT_5M;
      }
      break;
    default:
      {
        csSize.cx = csSize.cy = 0;
      }
      break;
    }
  }
  return csSize;
}

CSize CToolboxStack::getTitleButtonMargin(MonitorResolutionType enumResType, 
                                          BOOL bVertical) const
{
  CSize csMargin;
  if(bVertical)
  {
    switch (enumResType)
    {
    case MonitorResolution_Normal:
      {
        csMargin.cx = 0;
        csMargin.cy = V_TB_BTN_TOP_MARGIN_NORMAL;
      }
      break;
    case MonitorResolution_3M:
      {
        csMargin.cx = 0;
        csMargin.cy = V_TB_BTN_TOP_MARGIN_3M;
      }
      break;
    case MonitorResolution_5M:
      {
        csMargin.cx = 0;
        csMargin.cy = V_TB_BTN_TOP_MARGIN_5M;
      }
      break;
    default:
      {
        csMargin.cx = csMargin.cy = 0;
      }
      break;
    }
  }
  else
  {
    switch (enumResType)
    {
    case MonitorResolution_Normal:
      {
        csMargin.cx = H_TB_BTN_TOP_MARGIN_NORMAL;
        csMargin.cy = H_TB_BTN_LEFT_MARGIN_NORMAL;
      }
      break;
    case MonitorResolution_3M:
      {
        csMargin.cx = H_TB_BTN_TOP_MARGIN_3M;
        csMargin.cy = H_TB_BTN_LEFT_MARGIN_3M;
      }
      break;
    case MonitorResolution_5M:
      {
        csMargin.cx = H_TB_BTN_TOP_MARGIN_5M;
        csMargin.cy = H_TB_BTN_LEFT_MARGIN_5M;
      }
      break;
    default:
      {
        csMargin.cx = csMargin.cy = 0;
      }
      break;
    }
  }
  return csMargin;
}

void CToolboxStack::destroyStackItem(CToolboxStackItem& cItem)
{
  if(::IsWindow(cItem.wndToolbox.GetSafeHwnd()))
    cItem.wndToolbox.DestroyWindow();

  if(::IsWindow(cItem.btnTitle.GetSafeHwnd()))
    cItem.btnTitle.DestroyWindow();
}

void CToolboxStack::destroyAllStackItems()
{
  for_each(m_listItems.begin(), m_listItems.end(),
    [this](CToolboxStackItem* pItem)
  {
    this->destroyStackItem(*pItem);
  });
}

void CToolboxStack::removeAllStackItems()
{
  for_each(m_listItems.begin(), m_listItems.end(),
    [this](CToolboxStackItem* pItem)
  {
    this->destroyStackItem(*pItem);
    _DELETE(pItem);
  });
  m_listItems.clear();
}

BOOL CToolboxStack::validateCreateParams(const CreateParams& stParams)
{
  if(stParams.enumResType != MonitorResolution_Normal &&
     stParams.enumResType != MonitorResolution_3M     &&
     stParams.enumResType != MonitorResolution_5M)
     return FALSE;

  return TRUE;
}

BEGIN_MESSAGE_MAP(CToolboxStack, CWnd)
  ON_WM_CREATE()
  ON_WM_SIZE()
  ON_WM_PAINT()
  ON_WM_ERASEBKGND()
  ON_WM_DESTROY()
END_MESSAGE_MAP()

// CToolboxStack message handlers
int CToolboxStack::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
  if(CWnd::OnCreate(lpCreateStruct) == -1)
    return -1;

  CreateParams* pParams = reinterpret_cast<CreateParams*>(lpCreateStruct->lpCreateParams);

  if(pParams == nullptr || !validateCreateParams(*pParams))
    return - 1;

  DWORD dwError = ERROR_SUCCESS;
  unsigned nItemIndex = 0;
  for(auto itemPos = m_listItems.begin(), endPos = m_listItems.end();
      itemPos != endPos;
      ++itemPos, ++nItemIndex)
  {
    CToolboxStackItem* pItem = (*itemPos);
    dwError = createToolboxStackItem(*pItem, IDC_TOOLBOX_STACK_ITEM + nItemIndex);
    if(dwError != ERROR_SUCCESS) break;

    pItem->bOpen = (nItemIndex == 0 ? TRUE : FALSE);
  }

  if(dwError != ERROR_SUCCESS)
  {
    destroyAllStackItems();
    return -1;
  }

  m_enumResType = pParams->enumResType;
  return 0;
}

void CToolboxStack::OnSize(UINT nType, int cx, int cy)
{
  CWnd::OnSize(nType, cx, cy);

  if(cx > 0 && cy > 0)
    rearrangeStackItems(FALSE);
}

void CToolboxStack::OnPaint()
{
  CPaintDC dc(this);

  CRect rcClient;
  GetClientRect(&rcClient);
  //TODO(Jong Min, Lee): get toolbox stack color
  dc.FillSolidRect(&rcClient, RGB(45, 45, 48));
}

BOOL CToolboxStack::OnEraseBkgnd(CDC* pDC)
{
  UNUSED_ALWAYS(pDC);
  return TRUE;
}

void CToolboxStack::OnDestroy()
{
  destroyAllStackItems();
  CWnd::OnDestroy();
}

BOOL CToolboxStack::OnCommand(WPARAM wParam, LPARAM lParam)
{
  BOOL bReturn = CWnd::OnCommand(wParam, lParam);

  int nCode = HIWORD(wParam);
  if(nCode == BN_CLICKED)
  {
    UINT uID = LOWORD(wParam);
    if(uID >= IDC_TOOLBOX_STACK_ITEM &&
       uID - IDC_TOOLBOX_STACK_ITEM < m_listItems.size())
    {
      int nItemIndex = uID - IDC_TOOLBOX_STACK_ITEM;
      openToolboxStackItem(nItemIndex);
    }
  }
  return bReturn;
}